package qy.qyalgotrader.technical;

import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;

import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.event.EventHandler;
import qy.jalgotrade.utils.CommonUtils;

/**
 * <pre>计算例如成交量累计同比系数 (AAR)</pre>
 *
 * <pre>
 *  1. 样本: 时间段: [从 baseTm 开始, 到最近一个 bar time 结束]; 交易日: 近 period 个交易日
 *  (若 inclCurrDay 为 True, 则计算 period 包含当前交易日);
 *  </pre>
 *
 *  <pre>
 *  2. 使用 pandas.DataFrame 计算:
 *  </pre>
 */
public class IntraDayVolAccuRatio implements EventHandler {

    /**
     * 开盘 bar 时间
     */
    private final LocalTime __baseTime;

    /**
     * 样本交易日数
     */
    private final int __period;

    /**
     * 当前交易日是否算在 self.__period
     */
    private final boolean __inclCurrDay;

    /**
     *
     */
    private SequenceDataSeries<Double> __dataSeries;

    private SequenceDataSeries<Double> __intraDayVolAccu;

    private SequenceDataSeries<Double> __volAccuRatio;

    /**
     *
     * @param dataSeries dataSeries
     * @param period period
     * @param baseTm baseTm
     * @param inclCurrDayP inclCurrDayP default: false
     * @param maxLenP maxLenP default: 0
     * @throws Exception
     */
    public IntraDayVolAccuRatio(SequenceDataSeries<Double> dataSeries, int period, LocalTime baseTm, Optional<Boolean> inclCurrDayP,
                                OptionalInt maxLenP) throws Exception {

        this(dataSeries, period, baseTm, inclCurrDayP.orElse(false), maxLenP.orElse(0));
    }

    /**
     *
     * @param dataSeries dataSeries
     * @param period period
     * @param baseTm baseTm
     * @param inclCurrDay inclCurrDay
     * @param maxLen maxLen
     * @throws Exception
     */
    public IntraDayVolAccuRatio(SequenceDataSeries<Double> dataSeries, int period, LocalTime baseTm, boolean inclCurrDay,
                                int maxLen) throws Exception {

        if (inclCurrDay) {
            assert period >= 2;
        } else {
            assert period >= 1;
        }

        __baseTime = baseTm;
        __period = period;
        __inclCurrDay = inclCurrDay;
        __dataSeries = dataSeries;
        __intraDayVolAccu = new SequenceDataSeries<>(maxLen, Double.class);
        __volAccuRatio = new SequenceDataSeries<>(maxLen, Double.class);
        // 源 dataSeries 的 new-value-event 触发 IntraDayMvModel 更新:
        __dataSeries.getNewValueEvent().subscribe(this);
    }

    @Override
    public void handle(String eventName, Map<String, Object> params) throws Exception {

        @SuppressWarnings("unchecked")
        SequenceDataSeries<Double> dataSeries = (SequenceDataSeries<Double>) params.get("dataSeries");
        ZonedDateTime dateTime = (ZonedDateTime) params.get("dateTime");
        double value = (double) params.get("value");
        __onNewValue(dataSeries, dateTime, value);
    }

    /**
     * TODO:
     *
     * @param dataSeries
     * @param dateTime
     * @param value
     * @throws Exception
     */
    private void __onNewValue(SequenceDataSeries<Double> dataSeries, ZonedDateTime dateTime, double value)
            throws Exception {

        // 仅当样本数据齐全时, 进行计算:
        List<ZonedDateTime> allSampleDms = new ArrayList<>();
        DoubleArrayList sampleValueGroup = new DoubleArrayList();
        DoubleArrayList allSampleValues = new DoubleArrayList();
        List<ZonedDateTime> allDms = dataSeries.getDateTimes();
        int ct = __inclCurrDay ? __period + 1 : __period;
        int currIdx = -1;
        boolean aligned = true;
        try {
            while (ct > 0) {
                // 收集样本:
                ZonedDateTime aDm = allDms.get(allDms.size() + currIdx);
                double aVal = dataSeries.__getitem__(currIdx);
                currIdx += -1;
                if (aDm.toLocalTime().compareTo(dateTime.toLocalTime()) == 0) {
                    aligned = true;
                }
                if (aligned) {
                    allSampleDms.add(aDm);
                    sampleValueGroup.add(aVal);
                }
                if (aDm.toLocalTime().compareTo(__baseTime) == 0) {
                    ct--;
                    aligned = false;
                    allSampleValues.add(CommonUtils.sum(sampleValueGroup));
                    sampleValueGroup.clear();
                }
            }
            // 计算均值, 标准差, n 倍标准差轨道:
            double mean = CommonUtils.mean(allSampleValues);
            double vaRatio = !CommonUtils.fuzzyEquals(mean, 0.0) ? allSampleValues.getDouble(0) / mean : Double.NaN;
            // 更新各个指标 dataseries:
            __intraDayVolAccu.appendWithDateTime(dateTime, allSampleValues.getDouble(0));
            __volAccuRatio.appendWithDateTime(dateTime, vaRatio);
        } catch (IndexOutOfBoundsException e) {
            // 样本数据不足:
        }
    }

    public SequenceDataSeries<Double> getIntraDayVolAccu() {

        return __intraDayVolAccu;
    }

    public SequenceDataSeries<Double> getVolAccuRatio() {

        return __volAccuRatio;
    }
}
